#include "getPortStatus.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>

#if defined(WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#endif

static void _freehostinfo(LPADDRINFO host_ai) {
    freeaddrinfo(host_ai);
}

static int _setNonBlock(int sockfd, BOOL bNonBlock) {
    int ret = 0;

#if defined(WIN32)
    unsigned long ul = bNonBlock;
    ret = ioctlsocket(sockfd, FIONBIO, &ul);
#else
    int flags = fcntl(sockfd, F_GETFL, 0);
    if(flags<0) {
        return FALSE;
    }
    if(bNonBlock) {
        flags = flags | O_NONBLOCK;
    } else {
        flags = flags & ~O_NONBLOCK;
    }
    fcntl(sockfd, F_SETFL, flags);
#endif

    return ret;
}

static int _connect(int sockfd, LPADDRINFO host_ai, struct timeval * timeout) {
    int ret;

    ret = connect(sockfd, host_ai->ai_addr, host_ai->ai_addrlen);
    if (ret == 0) {
        /* connection established */
        return CHECK_PORT_USED;
    }
#if defined(WIN32)
    int lastErr = WSAGetLastError();
    if(lastErr != WSAEWOULDBLOCK) {
        return CHECK_PORT_NOT_USED_ERROR;
    }
#else
    if (errno != EINPROGRESS) {
        return CHECK_PORT_NOT_USED_ERROR;
    }
#endif

    fd_set fdrset, fdwset;
    FD_ZERO( & fdrset);
    FD_SET(sockfd, & fdrset);
    fdwset = fdrset;

    ret = select(sockfd + 1, & fdrset, & fdwset, NULL,
            timeout->tv_sec + timeout->tv_usec > 0 ? timeout :
            NULL);

    if (ret == 0) {
        /* timeout */
        return CHECK_PORT_TIMEOUT;
    }
    if (ret < 0) {
        return CHECK_PORT_NOT_USED_ERROR;
    }

    if (FD_ISSET(sockfd, & fdrset) || FD_ISSET(sockfd, & fdwset)) {

        socklen_t errlen = 0;
    #if defined(WIN32)
        char error[256];
        errlen = 256;
        char* pErr = error;
    #else
        int error = 0;
        errlen = sizeof(int);
        int* pErr = &error;
    #endif

        ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, pErr, &errlen);
        if (ret != 0) {
            /* getsockopt error */
            return CHECK_PORT_USED_ERROR;
        }
#if defined(WIN32)
        if (strlen(error) > 0) {
            /* closed */
            return CHECK_PORT_NOT_USE;
        }
#else
        if (error != 0) {
            /* closed */
            return CHECK_PORT_NOT_USE;
        }
#endif

        /* connection established */
        return CHECK_PORT_USED;

    } else {
        printf("fd unset rw events.\r\n");
        return CHECK_PORT_USED_ERROR;
    }
}

static int _closeSocket(int sockfd) {
    return close(sockfd);
}

int getPortStatus(char * pszHost, char * pszPort) {
    struct timeval timeout;
    // 1 秒=1000000 微秒
    // 1 毫秒=1000 微秒
    timeout.tv_sec = 0;
    timeout.tv_usec = 500000;

    int err;
    int retval = 0;

    char name[INET_HOSTNAME_LEN];
    char serv[INET_NUMERICSERVSTRLEN];
    ADDRINFO hints;

    memset( & name, 0, INET_HOSTNAME_LEN);
    memset( & serv, 0, INET_NUMERICSERVSTRLEN);

    LPADDRINFO host_ai = NULL;

    memset( & hints, 0, sizeof(ADDRINFO));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    do{
        // 1) resolve DNS hostname
        if ((err = getaddrinfo(pszHost, pszPort, & hints, & host_ai)) != 0) {
            retval = CHECK_PORT_COMMON_ERROR;
            break;
        }

        if ((err = getnameinfo(host_ai->ai_addr, host_ai->ai_addrlen,
                (char * ) & name, INET_HOSTNAME_LEN,
                (char * ) & serv, INET_NUMERICSERVSTRLEN,
                NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
            retval = CHECK_PORT_COMMON_ERROR;
            break;
        }

        // 2) open socket with None BLock
        int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        _setNonBlock(sockfd, TRUE);

        // 3) check socket is opened
        int result = _connect(sockfd, host_ai, & timeout);

        // 4) close the socket
        _closeSocket(sockfd);
        switch (result) {
            case CHECK_PORT_NOT_USED_ERROR:
                retval = CHECK_PORT_NOT_USED_ERROR;
                break;
            case CHECK_PORT_USED_ERROR:
                retval = CHECK_PORT_USED_ERROR;
                break;
            case CHECK_PORT_USED:
                retval = CHECK_PORT_USED;
                break;
            case CHECK_PORT_NOT_USE:
                retval = CHECK_PORT_NOT_USE;
                break;
            case CHECK_PORT_TIMEOUT:
                retval = CHECK_PORT_TIMEOUT;
                break;
            default:
                retval = CHECK_PORT_USED_ERROR;
                break;
        }
    }while(FALSE);
    // 5) free
        _freehostinfo(host_ai);
    return retval;
}